﻿// -----------------------------------------------------------------------
// <copyright file="NeighbourhoodOperations.cs" company="Image Processing Course Project">
// Created 28/10/2011
// </copyright>
// -----------------------------------------------------------------------

namespace ImageProcessing.IntermediateClasses
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using ImageProcessing.IntermediateClasses;
    using ImageProcessing.Utilities;

    /// <summary>
    /// Class for pixel Neighbourhood Operations
    /// </summary>
    public class NeighbourhoodOperations
    {
        /// <summary>
        /// Bitmap buffer for storing image values after padding
        /// </summary>
        private Colour.ColorsStruct[,] paddedBuffer;

        /// <summary>
        /// Size of mask
        /// </summary>
        private int maskSize;

        /// <summary>
        /// Bitmap buffer to store image for post processing
        /// </summary>
        private Colour.ColorsStruct[,] buffer;

        /// <summary>
        /// Float array carrying mask values
        /// </summary>
        private double[,] maskArray;

        /// <summary>
        /// Hight and width of new padded image
        /// </summary>
        private int newHeight, newWidth;

        /// <summary>
        /// Minimum Color values
        /// </summary>
        private int minimumRed = 255, minimumGreen = 255, minimumBlue = 255;

        /// <summary>
        /// Maximum Color values
        /// </summary>
        private int maximumRed = 0, maximumGreen = 0, maximumBlue = 0;

        /// <summary>
        /// Bitmap old image before padding or convolution
        /// </summary>
        private Bitmap oldImage;

        /// <summary>
        /// Initializes a new instance of the NeighbourhoodOperations class.
        /// </summary>
        /// <param name="oldImage">Old Image</param>
        /// <param name="maskSize">Size of mask</param>
        public NeighbourhoodOperations(Bitmap oldImage, int maskSize)
        {
            this.maskSize = maskSize;
            this.oldImage = oldImage;
        }

        /// <summary>
        /// Enum containing types of line detection
        /// </summary>
        public enum LineDetectionType
        {
            /// <summary>
            /// Horizontal Line Edge detection
            /// </summary>
            Horizontal,

            /// <summary>
            /// Vertical Line Edge detection
            /// </summary>
            Vertical,

            /// <summary>
            /// Left Diagonal Line Edge detection
            /// </summary>
            LeftDiagonal,

            /// <summary>
            /// Right Diagonal Line Edge detection
            /// </summary>
            RightDiagonal
        }

        /// <summary>
        /// Enumerator carrying post processing types
        /// </summary>
        public enum PostProcessing
        {
            /// <summary>
            /// No Post Processing
            /// </summary>
            none,

            /// <summary>
            /// Post Processing type Cut Off
            /// </summary>
            cutOff,

            /// <summary>
            /// Post Processing type normalization
            /// </summary>
            normalization
        }

        /// <summary>
        /// Padding image to solve border effect
        /// </summary>
        /// <param name="oldImage">Bitmap image to be padded</param>
        /// <returns>ColorsStruct padded image</returns>
        public Colour.ColorsStruct[,] Padding(Bitmap oldImage)
        {
            this.newWidth = this.oldImage.Width + (this.maskSize - 1);
            this.newHeight = this.oldImage.Height + (this.maskSize - 1);
            try
            {
                this.paddedBuffer = new Colour.ColorsStruct[this.newWidth, this.newHeight];
                for (int i = 0; i < (int)(this.maskSize / 2); i++)
                {
                    for (int k = 0; k < (int)(this.maskSize / 2); k++)
                    {
                        this.paddedBuffer[k, i].Red = this.oldImage.GetPixel(0, 0).R;
                        this.paddedBuffer[k, i].Green = this.oldImage.GetPixel(0, 0).G;
                        this.paddedBuffer[k, i].Blue = this.oldImage.GetPixel(0, 0).B;
                    }

                    for (int j = 0; j < this.oldImage.Width; j++)
                    {
                        this.paddedBuffer[j + (this.maskSize / 2), i].Red = this.oldImage.GetPixel(j, i).R;
                        this.paddedBuffer[j + (this.maskSize / 2), i].Green = this.oldImage.GetPixel(j, i).G;
                        this.paddedBuffer[j + (this.maskSize / 2), i].Blue = this.oldImage.GetPixel(j, i).B;
                    }

                    for (int k = (int)(this.maskSize / 2) + this.oldImage.Width; k < this.newWidth; k++)
                    {
                        this.paddedBuffer[k, i].Red = this.oldImage.GetPixel(this.oldImage.Width - 1, i).R;
                        this.paddedBuffer[k, i].Green = this.oldImage.GetPixel(this.oldImage.Width - 1, i).G;
                        this.paddedBuffer[k, i].Blue = this.oldImage.GetPixel(this.oldImage.Width - 1, i).B;
                    }
                }

                for (int i = 0; i < this.oldImage.Height; i++)
                {
                    for (int k = 0; k < (int)(this.maskSize / 2); k++)
                    {
                        this.paddedBuffer[k, i + (int)(this.maskSize / 2)].Red = this.oldImage.GetPixel(0, i).R;
                        this.paddedBuffer[k, i + (int)(this.maskSize / 2)].Green = this.oldImage.GetPixel(0, i).G;
                        this.paddedBuffer[k, i + (int)(this.maskSize / 2)].Blue = this.oldImage.GetPixel(0, i).B;
                    }

                    for (int j = 0; j < this.oldImage.Width; j++)
                    {
                        this.paddedBuffer[j + (this.maskSize / 2), i + (this.maskSize / 2)].Red = this.oldImage.GetPixel(j, i).R;
                        this.paddedBuffer[j + (this.maskSize / 2), i + (this.maskSize / 2)].Green = this.oldImage.GetPixel(j, i).G;
                        this.paddedBuffer[j + (this.maskSize / 2), i + (this.maskSize / 2)].Blue = this.oldImage.GetPixel(j, i).B;
                    }

                    for (int k = ((int)this.maskSize / 2) + this.oldImage.Width; k < this.newWidth; k++)
                    {
                        this.paddedBuffer[k, i + (this.maskSize / 2)].Red = oldImage.GetPixel(oldImage.Width - 1, i).R;
                        this.paddedBuffer[k, i + (this.maskSize / 2)].Green = oldImage.GetPixel(oldImage.Width - 1, i).G;
                        this.paddedBuffer[k, i + (this.maskSize / 2)].Blue = oldImage.GetPixel(oldImage.Width - 1, i).B;
                    }
                }

                for (int i = this.oldImage.Height + (int)(this.maskSize / 2); i < this.newHeight; i++)
                {
                    for (int k = 0; k < (int)(this.maskSize / 2); k++)
                    {
                        this.paddedBuffer[k, i].Red = this.oldImage.GetPixel(0, this.oldImage.Height - 1).R;
                        this.paddedBuffer[k, i].Green = this.oldImage.GetPixel(0, this.oldImage.Height - 1).G;
                        this.paddedBuffer[k, i].Blue = this.oldImage.GetPixel(0, this.oldImage.Height - 1).B;
                    }

                    for (int j = 0; j < this.oldImage.Width; j++)
                    {
                        this.paddedBuffer[j + (this.maskSize / 2), i].Red = this.oldImage.GetPixel(j, this.oldImage.Height - 1).R;
                        this.paddedBuffer[j + (this.maskSize / 2), i].Green = this.oldImage.GetPixel(j, this.oldImage.Height - 1).G;
                        this.paddedBuffer[j + (this.maskSize / 2), i].Blue = this.oldImage.GetPixel(j, this.oldImage.Height - 1).B;
                    }

                    for (int k = (int)(this.maskSize / 2) + this.oldImage.Width; k < this.newWidth; k++)
                    {
                        this.paddedBuffer[k, i].Red = this.oldImage.GetPixel(this.oldImage.Width - 1, this.oldImage.Height - 1).R;
                        this.paddedBuffer[k, i].Green = this.oldImage.GetPixel(this.oldImage.Width - 1, this.oldImage.Height - 1).G;
                        this.paddedBuffer[k, i].Blue = this.oldImage.GetPixel(this.oldImage.Width - 1, this.oldImage.Height - 1).B;
                    }
                }

                return this.paddedBuffer;
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Convolution function with any mask size
        /// </summary>
        /// <param name="mask">Mask 2 dimentional array</param>
        /// <param name="postProcessing">Post processing type</param>
        /// <returns>Bitmap image</returns>
        public Bitmap Convolution(double[,] mask, PostProcessing postProcessing)
        {
            try
            {
                this.Padding(this.oldImage);
                IntermediateClasses.PostProcessing postPro = new IntermediateClasses.PostProcessing();
                this.buffer = new Colour.ColorsStruct[this.oldImage.Width, this.oldImage.Height];
                int maskHorizontal = this.maskSize / 2;
                int maskVertical = this.maskSize / 2;

                for (int i = 0; i < this.oldImage.Height; i++)
                {
                    for (int j = 0; j < this.oldImage.Width; j++)
                    {
                        double redValue = 0, greenValue = 0, blueValue = 0;
                        for (int k = 0; k < this.maskSize; k++)
                        {
                            for (int l = 0; l < this.maskSize; l++)
                            {
                                redValue += mask[k, l] * this.paddedBuffer[j + l, i + k].Red;
                                greenValue += mask[k, l] * this.paddedBuffer[l + j, i + k].Green;
                                blueValue += mask[k, l] * this.paddedBuffer[l + j, i + k].Blue;
                            }
                        }

                        if (postProcessing == PostProcessing.cutOff)
                        {
                            redValue = postPro.CutOff(redValue);
                            greenValue = postPro.CutOff(greenValue);
                            blueValue = postPro.CutOff(blueValue);
                        }

                        this.buffer[j, i].Red = (int)redValue;
                        this.buffer[j, i].Green = (int)greenValue;
                        this.buffer[j, i].Blue = (int)blueValue;

                        if (this.minimumRed > redValue)
                        {
                            this.minimumRed = (int)redValue;
                        }
                        else
                            if (this.maximumRed < redValue)
                            {
                                this.maximumRed = (int)redValue;
                            }

                        if (this.minimumGreen > greenValue)
                        {
                            this.minimumGreen = (int)greenValue;
                        }
                        else
                            if (this.maximumGreen < greenValue)
                            {
                                this.maximumGreen = (int)greenValue;
                            }

                        if (this.minimumBlue > blueValue)
                        {
                            this.minimumBlue = (int)blueValue;
                        }
                        else
                            if (this.maximumBlue < blueValue)
                            {
                                this.maximumBlue = (int)blueValue;
                            }
                    }
                }

                if (postProcessing == PostProcessing.normalization)
                {
                    this.buffer = postPro.Normalization(this.buffer, this.oldImage.Width, this.oldImage.Height);
                }

                return this.ResultedImage(this.buffer);
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Method that converts the image buffer into a bitmap image
        /// </summary>
        /// <param name="inputBuffer">Image buffer</param>
        /// <returns>Bitmap image</returns>
        public Bitmap ResultedImage(Colour.ColorsStruct[,] inputBuffer)
        {
            try
            {
                Bitmap image = new Bitmap(this.oldImage.Width, this.oldImage.Height);
                for (int i = 0; i < this.oldImage.Height; i++)
                {
                    for (int j = 0; j < this.oldImage.Width; j++)
                    {
                        image.SetPixel(j, i, Color.FromArgb(inputBuffer[j, i].Red, inputBuffer[j, i].Green, inputBuffer[j, i].Blue));
                    }
                }

                return image;
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// LaPlacian Edge Detection Image Operation
        /// </summary>
        /// <returns>Bitmap Image</returns>
        public Bitmap LaPlacianEdgeDetection()
        {
            try
            {
                this.maskArray = new double[this.maskSize, this.maskSize];
                for (int i = 0; i < this.maskSize; i++)
                {
                    for (int j = 0; j < this.maskSize; j++)
                    {
                        if (i == (int)this.maskSize / 2 && j == (int)this.maskSize / 2)
                        {
                            this.maskArray[i, j] = 8;
                        }
                        else
                        {
                            this.maskArray[i, j] = -1;
                        }
                    }
                }

                return this.Convolution(this.maskArray, PostProcessing.normalization);
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// 4 Kirsch Compass Line detection
        /// </summary>
        /// <param name="type">Enum Line detection type</param>
        /// <returns>Bitmap image</returns>
        public Bitmap KirschLineDetection(LineDetectionType type)
        {
            try
            {
                this.maskArray = new double[this.maskSize, this.maskSize];
                switch (type)
                {
                    case LineDetectionType.Horizontal:
                        this.maskArray[0, 0] = 5;
                        this.maskArray[0, 1] = 5;
                        this.maskArray[0, 2] = 5;
                        this.maskArray[1, 0] = -3;
                        this.maskArray[1, 1] = 0;
                        this.maskArray[1, 2] = -3;
                        this.maskArray[2, 0] = -3;
                        this.maskArray[2, 1] = -3;
                        this.maskArray[2, 2] = -3;
                        break;
                    case LineDetectionType.Vertical:
                        this.maskArray[0, 0] = 5;
                        this.maskArray[0, 1] = -3;
                        this.maskArray[0, 2] = -3;
                        this.maskArray[1, 0] = 5;
                        this.maskArray[1, 1] = 0;
                        this.maskArray[1, 2] = -3;
                        this.maskArray[2, 0] = 5;
                        this.maskArray[2, 1] = -3;
                        this.maskArray[2, 2] = -3;
                        break;
                    case LineDetectionType.LeftDiagonal:
                        this.maskArray[0, 0] = -3;
                        this.maskArray[0, 1] = 5;
                        this.maskArray[0, 2] = 5;
                        this.maskArray[1, 0] = -3;
                        this.maskArray[1, 1] = 0;
                        this.maskArray[1, 2] = 5;
                        this.maskArray[2, 0] = -3;
                        this.maskArray[2, 1] = -3;
                        this.maskArray[2, 2] = -3;
                        break;
                    case LineDetectionType.RightDiagonal:
                        this.maskArray[0, 0] = 5;
                        this.maskArray[0, 1] = 5;
                        this.maskArray[0, 2] = -3;
                        this.maskArray[1, 0] = 5;
                        this.maskArray[1, 1] = 0;
                        this.maskArray[1, 2] = -3;
                        this.maskArray[2, 0] = -3;
                        this.maskArray[2, 1] = -3;
                        this.maskArray[2, 2] = -3;
                        break;
                    default:
                        break;
                }

                return this.Convolution(this.maskArray, PostProcessing.normalization);
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Generates mask of MeanFilter
        /// </summary>
        /// <returns>Bitmap Image</returns>
        public Bitmap MeanFilter()
        {
            try
            {
                this.maskArray = new double[this.maskSize, this.maskSize];
                float value = (float)(1 / Math.Pow(this.maskSize, 2.0));
                for (int i = 0; i < this.maskSize; i++)
                {
                    for (int j = 0; j < this.maskSize; j++)
                    {
                        this.maskArray[i, j] = value;
                    }
                }

                return this.Convolution(this.maskArray, PostProcessing.none);
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Generates mask of GaussianFilter
        /// </summary>
        /// <param name="sigma">Float sigma</param>
        /// <returns>Bitmap image</returns>
        public Bitmap GaussianFilter(float sigma)
        {
            try
            {
                this.CalculateGaussianMaskValues(sigma);
                return this.Convolution(this.maskArray, PostProcessing.none);
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Calculate Gaussian Mask value
        /// </summary>
        /// <param name="sigma">Float sigma</param>
        public void CalculateGaussianMaskValues(float sigma)
        {
            try
            {
                this.maskArray = new double[this.maskSize, this.maskSize];
                int negRange = (-1) * (this.maskSize / 2);
                int posRange = this.maskSize / 2;
                float x = negRange, y = negRange;
                double sum = 0;
                for (int i = 0; i < this.maskSize; i++)
                {
                    x = negRange;
                    for (int j = 0; j < this.maskSize; j++)
                    {
                        double expression1 = (Math.Pow(x, 2) + Math.Pow(y, 2)) / (2 * Math.Pow(sigma, 2));
                        double result = (1 / (2 * Math.PI * Math.Pow(sigma, 2))) * Math.Exp((-1) * expression1);
                        this.maskArray[i, j] = result;
                        sum += result;
                        x += 1;
                    }

                    y += 1;
                }
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }
    }
}
